Skip to content

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class - #15165

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket
Sep 4, 2026
Merged

tooling(pm): give dispatch-gates --ran an explained bucket for the value-bearing class#15165
os-steve merged 1 commit into
mainfrom
claude/issue-15115-ran-value-bearing-bucket

Conversation

@claude

@claude claude Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #15115

runReconciliation in scripts/pm/dispatch-gates.mjs classifies a command the
runner RECORDED that is not in the derived runnable set. It had three outcomes,
two of which are "explained, not a mistake": a CI-measured-only family, and a
pending-changeset family. The value-bearing class — an invocation whose argv
takes a value from the workflow, added by PR #15114 and subtracted from
--commands by the second subtraction in commandsFor — had no such bucket, so
a recorded one landed in extra.

Reproduced at the merge base, before any edit

Merge base 4283b72a. Record = the 15 commands --commands derives for a
changeset path, plus the live pr-automation.yml invocation
node scripts/check-empty-changeset.mjs --base "$MERGE_BASE":

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
    --ran repro-ran.list .changeset/foo.md

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
  Outside this card's derivation (1) — recorded, and named by nothing this run derived. Not an error: a run beyond the union costs nothing.
    - node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

That caption is the defect in one sentence: this run DID derive that family and
then classified it out of the union on purpose, so "named by nothing this run
derived" is the one thing that is false about it. No reason is attached, and the
runner is left to guess whether they typed something wrong.

The bucket

  • runReconciliation takes notRunnableCommands, defaulting to an empty set so
    every existing caller's verdict is byte-identical.
  • It is checked BESIDE ciOnlyCommands and before pendingCommands, because
    the file already groups those two: they are the two subtractions commandsFor
    makes from the runnable union, in that order, while the pending families are a
    different fact (a path that did not exist at derivation time). ciOnly keeps
    first precedence, matching commandsFor, so one command can never produce two
    counts for one omission.
  • The set is built at the --ran call site by notRunnableCommandSet(matchedRows, alwaysRunsRows) — the SAME expression commandsFor subtracts with, off the
    same rows — so the union and the reconciliation cannot drift about which
    invocations are withheld.
  • recon.ok still reads unrun.length only. --commands output, the
    classifier, the derivation keys and every live key are untouched.

Rendered on its own labelled line, in the shape the two beside it use. Same
record, same card, on this branch:

Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
  Classified by this tool, no explanation owed (1) — VALUE-BEARING famil(ies): its argv takes a value from the workflow, so it is recorded, not derived as runnable:
    - node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED.

The remainder heading is gone entirely and the verdict is unmoved.

Self-test — 7 new cases, 1353 total

Unit, beside the existing explained case (whose fixture now carries a fourth
recorded entry, the live value-bearing spelling):

  • a recorded value-bearing command classifies into explainedNotRunnable and is
    NOT in extra;
  • CONTROL: an unknown command recorded beside it still reads extra — the
    bucket explains its class and nothing else, which is the half that keeps this
    from deleting the remainder rather than shrinking it;
  • the two existing buckets keep their verdicts in the same run
    (explainedCiOnly 1, explainedPending 1, extra 0);
  • the bucket cannot move the verdict: ok true, unrun 0;
  • the rendering carries its own labelled line, the reason, and the command, with
    the two neighbouring lines still present and the remainder heading absent.

End to end, a real CLI run (three cases), because every unit case above stays
green if the --ran call site never PASSES the set — a parameter defaulting to
empty is exactly the shape that keeps its own tests green while the live mode
still dumps the class in the remainder. Its fixture is the tool's own --json
answer on this tree rather than a spelling typed into the test, so it cannot
outlive a change to the renderer that produces the real invocation.

Gates — all green, run at head 5fb6e3a7

Re-derived from the real change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) after the commit; the
20 families match the dispatch list exactly, nothing added. Each exit code was
captured by redirecting first, never after a pipe; every line below is the
gate's own verdict line.

gate verdict line
check-ci-filter-parity.mjs OK: all 143 declared cross-package glob(s) (99 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.
check-closing-keyword-parity.mjs check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8245 tracked file(s), all registered).
check-closing-keyword-parity.mjs --self-test ✓ 24 assertions, 5 mutations of the shipped parsers each driven to red.
check-comment-mask-corpus.mjs ✓ comment-mask corpus sweep: 5858 files, 0 disagree, 0 unparseable, 49.0s (comparator self-test: 12 cases pass).
check-self-test-wired.mjs ✓ every one of the 166 script(s) CI runs that ship a --self-test has that self-test run by CI.
check-self-test-wired.mjs --self-test 4 live ledger row(s) verified ... 7 declared batteries, 50 cases registered, every battery at or above its pinned floor.
check-whole-set-label-write.mjs ✓ 0 violations — 258 file(s) over 3 root(s) · 160 uses: pin(s) over 18 distinct action(s) judged · 0 allowlist entr(ies)
check-whole-set-label-write.mjs --self-test ✓ all cases pass (24 fixture trees + 5 refusals + 1 allowlist hatch)
pnpm check:agent-test-spelling ✓ 0 violations — 438 file(s) · 5916 bare -- token(s) · 1408 launcher-rooted run(s) · 9 separator(s) JUDGED
pnpm check:bash32-floor ✓ self-test: 153 cases pass. / ✓ 26 tracked shell file(s) ... name no bash 4+ construct
pnpm check:cli-command-ids ✓ 324 command-id literal(s) across 114 file(s) outside packages/cli all resolve to a real command path
pnpm check:cross-package-test-inputs OK: 26 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
pnpm check:declared-population-live ✓ 198 of 249 famil(ies) declare a path population, and every one of them reaches this tree's 8245 tracked file(s).
pnpm check:entry-guard ✓ 207 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 153 export bindings, 153 of them inert on import
pnpm check:nul-bytes check-nul-bytes: OK (scanned 8238 text file(s) ... no raw ASCII control bytes).
pnpm check:parse-guard ✓ 206 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.
pnpm check:pm-dispatch-gates ✓ dispatch-gates self-test: 1353 cases pass.
pnpm check:pnpm-filter-targets ✓ 142/181 --filter occurrence(s) across 33 file(s) resolve against 79 workspace package(s)
pnpm check:refd-timer-probe OK 5853 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.
pnpm check:watch-hint-literal ✓ 48 declaration(s) across 4 rostered name(s) ... no unrostered spelling of the idiom in the tree.
bare-root-worklist.mjs --self-test (not in the derivation; asserted green because a parallel flight edits that file) OK self-test: 54 live row(s), 46 unreachable as spelled, 46 recorded verdict(s) — none stale, none missing, none contradicted.

Repo-wide ESLint, not narrowed: pnpm lint (eslint . --no-inline-config) through
the shared verify lock — os-verify-lock: VERDICT command-exit 0 · held the lock 66s.
The dispatch-gates --self-test also ran through the lock: VERDICT command-exit 0 · held the lock 354s. Both wall-clock figures are shared-box seconds, as the lock's
own verdict line states.

NOT MEASURED, in its own words: check-test-completeness is itself one of this
diff's value-bearing families — its argv takes $RUNNER_TEMP from the workflow,
so it is outside --commands and has no local run. Same for
check-shard-attestation.mjs --emit ... and the --union-into spelling of
check-cross-package-test-inputs (the bare pnpm check:cross-package-test-inputs
spelling of that family IS in the table above and ran green).

Nothing is published from any package (scripts/pm/**, no .md), so
skip-changeset is applied.


Generated by Claude Code

…lue-bearing class

`runReconciliation` classified a recorded command that is not in the derived
runnable set into three outcomes, two of which are "explained, not a mistake":
a CI-measured-only family and a pending-changeset family. The value-bearing
class added for #15083 — an invocation whose argv takes a value from the
workflow, which `commandsFor` subtracts from the union for the same reason it
subtracts the CI-measured one — had no such bucket, so a dev who recorded one
landed in `extra` under a caption that reads "named by nothing this run
derived": the one sentence that is false about it, because this run derived it
and then classified it out.

Third bucket, built from `notRunnableCommandSet` off the same rows the
CI-measured set is built from, checked beside it (the two subtractions
`commandsFor` makes, in that order) and before the `extra` fallback, and
rendered on its own labelled line naming the reason. Diagnostic only:
`recon.ok` still reads `unrun.length`, `--commands` is untouched, and no
derivation key moves.

Measured at the merge base 4283b72: a record of the 15 derived commands for a
changeset path plus the live `node scripts/check-empty-changeset.mjs --base
"$MERGE_BASE"` invocation reported

  Outside this card's derivation (1) — recorded, and named by nothing this run
  derived.
    - node scripts/check-empty-changeset.mjs --base "$MERGE_BASE"

and the same record on this tree reports the labelled bucket instead, with the
remainder heading gone and the verdict unmoved (15 derived, 15 run, exit 0).

Self-test: seven cases, three of them a real CLI run whose fixture is the
tool's own `--json` answer on this tree — a parameter defaulting to empty is
exactly the shape that keeps unit cases green while the live mode still dumps
the class in the remainder.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants